Amazon Inspectorの未改善の脆弱性検出結果をAWS Security Hub経由で、週次でメール通知してみた
はじめに
マルチアカウント環境でAWS Security Hubに統合されたAmazon Inspectorの未改善の脆弱性検出結果を、週次でメール通知する方法をご紹介します。
Amazon Inspectorでは、脆弱性の検出結果が一度だけ作成されます。
そのため、検出結果の作成をトリガーにAmazon EventBridge経由で通知する一般的な方法では、一度しか通知されず、その後の通知が行われません。
この仕組みでは、重要な脆弱性を見逃してしまうリスクがあります。
そのため、定期的に未改善の検出結果を通知する仕組みを構築する方法をご紹介します。
本記事では、マルチアカウント環境でInspectorがSecurity Hubと統合された構成を想定しています。
Amazon EventBridge Schedulerを使用して週次でAWS Lambdaを起動し、LambdaがInspectorの検出結果を集約しているSecurity Hubに対して未改善の検出結果をすべて取得し、アカウントごとに1通メール送信します。
EventBridge Scheduler、Lambda、Amazon SNSトピックは、Security Hub管理アカウントの集約リージョンで作成します。
前提条件
- Security Hubが有効化されていること。
- Inspectorが有効化されていること。
- 本記事では、ECRとLambdaのスキャンが有効化されており、EC2のスキャンは無効化されています。
- 通知用のSNSトピックが作成済みであること。
Lambda用のIAMポリシー
Lambdaで利用するIAMポリシーを作成します。
ポリシー名は、inspector-weekly-findings-notification-lambda-policyとします。
アカウントIDと通知用のSNSトピックARNは、ご利用の環境に合わせて適宜変更してください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"securityhub:GetFindings",
"securityhub:BatchGetSecurityControls",
"securityhub:GetSecurityControlDefinition"
],
"Resource": [
"arn:aws:securityhub:*:012345678901:hub/default",
"arn:aws:securityhub:*:012345678901:security-control/*",
"arn:aws:securityhub:*:012345678901:findings/*"
]
},
{
"Effect": "Allow",
"Action": "sns:Publish",
"Resource": "arn:aws:sns:ap-northeast-1:012345678901:test"
}
]
} ]
}
Lambda
以下の設定でLambdaを作成します。
- ランタイム: Python 3.13
- タイムアウト: 10秒
- IAMロールに付与するポリシー
- AWSLambdaBasicExecutionRole
- inspector-weekly-findings-notification-lambda-policy
以下にLambdaのコードを示します。通知用のSNSトピックARNは、各自の環境に合わせて適宜変更してください。
import boto3
SNS_TOPIC_ARN = 'arn:aws:sns:ap-northeast-1:0123456789012:test'
SEVERITY_LEVELS = ['CRITICAL', 'HIGH', 'MEDIUM']
def get_inspector_findings():
# Security Hubから検出結果を取得(ページネーション対応)
securityhub = boto3.client('securityhub')
all_findings = []
paginator = securityhub.get_paginator('get_findings')
for page in paginator.paginate(
Filters={
'ProductName': [{'Value': 'Inspector', 'Comparison': 'EQUALS'}],
'RecordState': [{'Value': 'ACTIVE', 'Comparison': 'EQUALS'}],
'SeverityLabel': [
{'Value': level, 'Comparison': 'EQUALS'}
for level in SEVERITY_LEVELS
],
'WorkflowStatus': [{'Value': 'NEW', 'Comparison': 'EQUALS'}]
}
):
all_findings.extend(page['Findings'])
return all_findings
def group_findings_by_account(findings):
# 検出結果をアカウントとリソースごとに整理
findings_by_account = {}
for finding in findings:
account_id = finding.get('AwsAccountId')
if account_id not in findings_by_account:
findings_by_account[account_id] = {
'findings': [],
'resources': {}
}
findings_by_account[account_id]['findings'].append(finding)
resource_id = finding['Resources'][0]['Id']
if resource_id not in findings_by_account[account_id]['resources']:
findings_by_account[account_id]['resources'][resource_id] = []
findings_by_account[account_id]['resources'][resource_id].append(finding)
return findings_by_account
def count_severity(findings):
severity_count = {level: 0 for level in SEVERITY_LEVELS}
for finding in findings:
severity = finding.get('Severity', {}).get('Label')
if severity in severity_count:
severity_count[severity] += 1
return severity_count
def create_message(account_id, account_data):
findings = account_data['findings']
total_findings = len(findings)
total_severity_count = count_severity(findings)
message_lines = [
f"Inspectorの脆弱性検出アラート - 未改善の脆弱性レポート",
f"アカウント: {account_id}",
f"\n※このレポートは、以下の条件に該当する脆弱性を表示しています:",
f"- 現在もアクティブな状態(修正未完了)",
f"- ワークフローステータスが「NEW」",
f"- 重要度が CRITICAL/HIGH/MEDIUM",
f"\n検出された未改善の脆弱性の概要:",
f"総脆弱性数: {total_findings}件"
]
# アカウント全体の重要度別件数
for severity in SEVERITY_LEVELS:
message_lines.append(f"- {severity}: {total_severity_count[severity]}件")
message_lines.append("\n【リソースごとの未改善の脆弱性件数】")
# リソースごとの重要度別件数
for resource_id, resource_findings in sorted(account_data['resources'].items()):
severity_count = count_severity(resource_findings)
message_lines.extend([
f"\n■ {resource_id}",
f" 総数: {len(resource_findings)}件"
])
for severity in SEVERITY_LEVELS:
message_lines.append(f" - {severity}: {severity_count[severity]}件")
message_lines.extend([
"\n※これらの脆弱性は、検出されてから改善されていない状態が継続しています。",
"早急な対応をご検討ください。"
])
return '\n'.join(message_lines)
def send_notifications(findings_by_account):
sns = boto3.client('sns')
for account_id, account_data in findings_by_account.items():
message = create_message(account_id, account_data)
sns.publish(
TopicArn=SNS_TOPIC_ARN,
Subject=f'Inspector週次レポート(未改善の脆弱性) - Account: {account_id}',
Message=message
)
def lambda_handler(event, context):
findings = get_inspector_findings()
findings_by_account = group_findings_by_account(findings)
send_notifications(findings_by_account)
return {
'statusCode': 200,
'body': 'Successfully processed Inspector findings'
}
このLambdaの処理の流れは以下のとおりです。
- Security Hubから未改善のInspector検出結果を取得
- ワークフローステータスが「新規(NEW)」
- 取得した検出結果をアカウントIDごとに分類
- 各アカウントのリソースごとに脆弱性を集計
- 重要度は、「CRITICAL」, 「HIGH」, 「MEDIUM」
- アカウントごとにレポート形式のメッセージを作成
- SNSトピック経由でアカウントごとにメール送信
EventBridge Scheduler
EventBridge Schedulerは以下の設定で作成します。
-
定期的なスケジュール
-
cron式:
0 9 ? * MON *
- 今回は、毎週月曜日の9:00にLambdaをキックします
-
フレックスタイムウィンドウ:オフ
-
ターゲットAPI:Lambda Invoke
-
対象のLambda:さきほど作成したLambda
-
ペイロード:空
他はデフォルトの設定です。IAMロールも自動作成します。
試してみる
設定したスケジュールの時間になると、Lambdaが起動し、アカウントごとにInspectorの検出結果を通知します。
通知内容は以下のイメージです。
Inspector週次レポート(未改善の脆弱性) - Account: 123456789012
Inspectorの脆弱性検出アラート - 未改善の脆弱性レポート
アカウント: 123456789012
※このレポートは、以下の条件に該当する脆弱性を表示しています:
- 現在もアクティブな状態(修正未完了)
- ワークフローステータスが「NEW」
- 重要度が CRITICAL/HIGH/MEDIUM
検出された未改善の脆弱性の概要:
総脆弱性数: 11件
- CRITICAL: 3件
- HIGH: 5件
- MEDIUM: 3件
【リソースごとの未改善の脆弱性件数】
■ arn:aws:ecr:ap-northeast-1:012345678901:repository/nginx-test/sha256:37c022aa2e42b98eb787cfe6be34e5457081c5b7693a4d8ea8fa43b2f07e1bbc
総数: 8件
- CRITICAL: 3件
- HIGH: 3件
- MEDIUM: 2件
■ arn:aws:ecr:ap-northeast-1:012345678901:repository/nginx/sha256:7ba542bde95e6523a4b126f610553e3657b8108bc3175596ee7e911ae1219bfc
総数: 3件
- CRITICAL: 0件
- HIGH: 2件
- MEDIUM: 1件
※これらの脆弱性は、検出されてから改善されていない状態が継続しています。
早急な対応をご検討ください。
--
最後に
本記事では、AWS Security Hubに集約されたAmazon Inspectorの未改善の脆弱性検出結果を週次でメール通知する仕組みを構築する方法をご紹介しました。
この仕組みを導入することで、重要な脆弱性を見逃すリスクを軽減し、セキュリティ対応の効率化が期待できます。
参考